﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Windows;
using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Messaging;
using HP.Cachalote.DataModels;
using HP.Cachalote.Services;
using HP.Cachalote.Services.Interfaces;
using HP.Cachalote.Services.Shared;
using HP.Cachalote.Services.Validations;
using HP.Cachalote.WPF.Model;
using HP.Cachalote.WPF.Shared;

namespace HP.Cachalote.WPF.ViewModel
{
    /// <summary>
    /// This class contains properties that a View can data bind to.
    /// <para>
    /// See http://www.galasoft.ch/mvvm
    /// </para>
    /// </summary>
    public class UnfinishedTodoItemTreeViewModel : MyViewModelBase
    {
        public enum TodoItemActionCategory
        {
            Done,
            Undone
        }

        public const string RelatedTodoItemsChangedMessageToken = "RelatedTodoItemsChanged";
        public const string TodoItemIsDoneMessageToken = "TodoItemIsDone";
        public const string DeleteItemConfirmMessageToken = "DeleteItemConfirm";

        public TodoItemActionCategory ActionCategory { get; private set; }

        // This FilterType for Fitler List update related unfinished TodoItem count only. No other purpose.
        public TodoItemFilterType FilterType { get; set; }

        private ITodoItemService _todoItemService;

        /// <summary>
        /// Initializes a new instance of the UnfinishedTodoItemListViewModel class.
        /// </summary>
        public UnfinishedTodoItemTreeViewModel(IDataService dataService)
            : base(dataService)
        {
            _todoItemService = ServiceFactory.CreateTodoItemService();

            // Load Today's TodoItems by default.
            LoadTodoItemsOfSelectedFilter(TodoItemFilterType.Today);
            SelectedTypeName = TodoItemFilterType.Today.ToString();
            SetItemInfoSummary();
        }

        /// <summary>
        /// Register all necessary messages. It is called by constructor in base class.
        /// </summary>
        protected override void RegisterMessages()
        {
            base.RegisterMessages();

            RegisterSelectToShowRelatedTodoItemsMessage();
            RegisterRequestDeleteItemMessage();
            RegisterRequestSetItemAsDoneMessage();
            RegisterRequestSetItemAsUndoneMessage();
            RegisterStartSearchTodoItemMessage();
        }

        /// <summary>
        /// The <see cref="CurrentTodoLabel" /> property's name.
        /// </summary>
        public const string CurrentTodoLabelPropertyName = "CurrentTodoLabel";

        private TodoLabel _currentTodoLabel = null;

        /// <summary>
        /// Sets and gets the CurrentTodoLabel property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public TodoLabel CurrentTodoLabel
        {
            get
            {
                return _currentTodoLabel;
            }

            set
            {
                if (_currentTodoLabel == value)
                {
                    return;
                }

                RaisePropertyChanging(CurrentTodoLabelPropertyName);
                _currentTodoLabel = value;
                RaisePropertyChanged(CurrentTodoLabelPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="NewAddedTodoItemTitle" /> property's name.
        /// </summary>
        public const string NewAddedTodoItemTitlePropertyName = "NewAddedTodoItemTitle";

        private string _newAddedTodoItemTitle = string.Empty;

        /// <summary>
        /// Sets and gets the NewAddedTodoItemTitle property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string NewAddedTodoItemTitle
        {
            get
            {
                return _newAddedTodoItemTitle;
            }

            set
            {
                if (_newAddedTodoItemTitle == value)
                {
                    return;
                }

                RaisePropertyChanging(NewAddedTodoItemTitlePropertyName);
                _newAddedTodoItemTitle = value;
                RaisePropertyChanged(NewAddedTodoItemTitlePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="SelectedTypeName" /> property's name.
        /// </summary>
        public const string SelectedTypeNamePropertyName = "SelectedTypeName";

        private string _selectedTypeName = string.Empty;

        /// <summary>
        /// Sets and gets the SelectedTypeName property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string SelectedTypeName
        {
            get
            {
                return _selectedTypeName;
            }

            set
            {
                if (_selectedTypeName == value)
                {
                    return;
                }

                RaisePropertyChanging(SelectedTypeNamePropertyName);
                _selectedTypeName = value;
                RaisePropertyChanged(SelectedTypeNamePropertyName);
            }
        }

        /// <summary>
        /// The <see cref="TodoItems" /> property's name.
        /// </summary>
        public const string TodoItemsPropertyName = "TodoItems";

        private ObservableCollection<TodoItemTreeViewItemViewModel> _todoItems =
            new ObservableCollection<TodoItemTreeViewItemViewModel>();

        /// <summary>
        /// Sets and gets the TodoItems property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public ObservableCollection<TodoItemTreeViewItemViewModel> TodoItems
        {
            get
            {
                return _todoItems;
            }

            set
            {
                if (_todoItems == value)
                {
                    return;
                }

                RaisePropertyChanging(TodoItemsPropertyName);
                _todoItems = value;
                RaisePropertyChanged(TodoItemsPropertyName);
            }
        }

        /// <summary>
        /// The <see cref="ItemInfoSummary" /> property's name.
        /// </summary>
        public const string ItemInfoSummaryPropertyName = "ItemInfoSummary";

        private string _itemInfoSummary = string.Empty;

        /// <summary>
        /// Sets and gets the ItemInfoSummary property.
        /// Changes to that property's value raise the PropertyChanged event. 
        /// </summary>
        public string ItemInfoSummary
        {
            get
            {
                return _itemInfoSummary;
            }

            set
            {
                if (_itemInfoSummary == value)
                {
                    return;
                }

                RaisePropertyChanging(ItemInfoSummaryPropertyName);
                _itemInfoSummary = value;
                RaisePropertyChanged(ItemInfoSummaryPropertyName);
            }
        }

        private RelayCommand _addNewTodoItemCommand;

        /// <summary>
        /// Gets the AddNewTodoItemCommand.
        /// </summary>
        public RelayCommand AddNewTodoItemCommand
        {
            get
            {
                return _addNewTodoItemCommand ?? (_addNewTodoItemCommand = new RelayCommand(
                    ExecuteAddNewTodoItemCommand,
                    CanExecuteAddNewTodoItemCommand));
            }
        }

        private void ExecuteAddNewTodoItemCommand()
        {
            try
            {
                // Prepare a new TodoItem instance.
                var newItem = new TodoItem
                {
                    Title = NewAddedTodoItemTitle,
                    IsDone = false,
                    NeedReminder = false,
                    IsDismissed = false,
                    CreateOn = GeneralUtilities.GetCurrentDateTime(),
                    UpdateOn = GeneralUtilities.GetCurrentDateTime(),
                    Priority = (int)TodoItemPriority.Low,
                    Label = new TodoLabel { ID = CurrentTodoLabel.ID, Name = CurrentTodoLabel.Name }
                };

                newItem.ID = (int)_todoItemService.Add(newItem);

                // Insert the TodoItem into database and get the ID back and create a 
                // new UnfinishedTodoItemTreeViewItemViewModel for TreeView binding.
                var newTreeViewItem = ViewModelHelper.CreateNewUnfinishedTodoItemTreeViewItemViewModel(CurrentDataService, newItem);

                InsertSelectedItemIntoCollection(newTreeViewItem);

                // Clear current input and be ready for the another one.
                ResetInput();
            }
            catch (System.Exception ex)
            {
                RequestDisplayErrorMessage(ex.Message);
            }
        }

        private bool CanExecuteAddNewTodoItemCommand()
        {
            var todoItem = new TodoItem
            {
                Title = NewAddedTodoItemTitle
            };

            var validator = new TodoItemValidator();
            validator.Validate(todoItem);

            if (CurrentTodoLabel == null || !validator.IsValid)
            {
                return false;
            }
            else
            {
                return true;
            }
        }

        private RelayCommand _cancelCommand;

        /// <summary>
        /// Gets the CancelCommand.
        /// </summary>
        public RelayCommand CancelCommand
        {
            get
            {
                return _cancelCommand
                    ?? (_cancelCommand = new RelayCommand(
                                          () =>
                                          {
                                              ResetInput();
                                          }));
            }
        }

        /// <summary>
        /// Load all related TodoItems of selected TodoLabel for binding on the UI.
        /// </summary>
        private void LoadTodoItemsOfSelectedTodoLabel()
        {
            var items = from item in _todoItemService.Filter(item => item.Label.ID == CurrentTodoLabel.ID && !item.IsDone, true)
                        select ViewModelHelper.CreateNewUnfinishedTodoItemTreeViewItemViewModel(CurrentDataService, item);

            TodoItems = new ObservableCollection<TodoItemTreeViewItemViewModel>(items);
        }

        /// <summary>
        /// Load all related TodoItems of selected filter for binding on the UI.
        /// </summary>
        private void LoadTodoItemsOfSelectedFilter(TodoItemFilterType filter)
        {
            var items = from item in _todoItemService.Filter(item => !item.IsDone, true).Where(item =>
                        filter == TodoItemFilterType.All || GeneralUtilities.GetFilterType(item.DueDate) == filter) 
                        orderby item.Label.ID ascending, item.DueDate ascending, item.StartTime ascending, item.EndTime ascending
                        select ViewModelHelper.CreateNewUnfinishedTodoItemTreeViewItemViewModel(CurrentDataService, item);

            TodoItems = new ObservableCollection<TodoItemTreeViewItemViewModel>(items);
        }

        /// <summary>
        /// Set the items statistics information.
        /// </summary>
        private void SetItemInfoSummary()
        {
            SetItemInfoSummary(-1);
        }

        /// <summary>
        /// Set the items statistics information.
        /// </summary>
        /// <param name="matchedItemCount">The mateched item count based on 
        /// provided key word for search. Use -1 if it is not applicabe.</param>
        private void SetItemInfoSummary(int matchedItemCount)
        {
            if (TodoItems.Count == 0)
            {
                ItemInfoSummary = "There are no items to display";
            }
            else if (TodoItems.Count == 1)
            {
                ItemInfoSummary = string.Format("{0} item", TodoItems.Count);
            }
            else
            {
                ItemInfoSummary = string.Format("{0} items", TodoItems.Count);
            }

            if (matchedItemCount != -1)
            {
                ItemInfoSummary += string.Format(", {0} Matched.", matchedItemCount);
            }
        }

        /// <summary>
        /// Clear current input and be ready for the another one.
        /// </summary>
        private void ResetInput()
        {
            NewAddedTodoItemTitle = string.Empty;
        }

        /// <summary>
        /// Once the TodoItems changed then need to notify all observers.
        /// </summary>
        /// <param name="actionCategory">What kind of action has been taken. Set TodoItem as done or undone.</param>
        private void TodoItemsChangedNotify(TodoItemActionCategory actionCategory)
        {
            ActionCategory = actionCategory;

            // Send Message to TodoItemFilterViewModel, TodoLabelTreeViewItemViewModel to notify the TodoItems Change.
            Messenger.Default.Send<UnfinishedTodoItemTreeViewModel>(this, RelatedTodoItemsChangedMessageToken);
        }

        /// <summary>
        /// Get the message of SelectToShowRelatedTodoItemsMessage from TodoLabelTreeViewModel 
        /// to show the TodoItems based on selected TodoLabel or TodoItemFilter.
        /// </summary>
        private void RegisterSelectToShowRelatedTodoItemsMessage()
        {
            Messenger.Default.Register<object>(this, TodoLabelTreeViewModel.SelectToShowRelatedTodoItemsMessageToken, (selectedType) =>
            {
                if (selectedType is TodoLabel)
                {
                    CurrentTodoLabel = selectedType as TodoLabel;
                    SelectedTypeName = CurrentTodoLabel.Name;

                    LoadTodoItemsOfSelectedTodoLabel();
                }
                else if (selectedType is TodoItemFilterType)
                {
                    var selectedFilter = (TodoItemFilterType)selectedType;
                    SelectedTypeName = selectedFilter.ToString();
                    CurrentTodoLabel = null;

                    LoadTodoItemsOfSelectedFilter(selectedFilter);
                }

                SetItemInfoSummary();
            });
        }

        /// <summary>
        /// Get the messger from TodoItemTreeViewItemViewModel and show a Dialog to user to confirm.
        /// </summary>
        private void RegisterRequestDeleteItemMessage()
        {
            Messenger.Default.Register<int>(this, TodoItemTreeViewItemViewModel.RequestDeleteItemMessageToken, itemID =>
                {
                    // Show a message to confirm whether user needs to delete the selected label.
                    var dialogMessage = new DialogMessage("Delete this item?", dialogResult =>
                        {
                            // This is the CallBack of the DialogMessage.
                            if (dialogResult == MessageBoxResult.Yes)
                            {
                                try
                                {
                                    DeleteSelectedItemFromDataSource(itemID);
                                    RemoveSelectedItemFromCollection(itemID);
                                }
                                catch (Exception ex)
                                {
                                    RequestDisplayErrorMessage(ex.Message);
                                }
                            }
                        })
                    {
                        Button = MessageBoxButton.YesNo,
                        Caption = "Delete Item",
                        Icon = MessageBoxImage.Question
                    };

                    Messenger.Default.Send<DialogMessage>(dialogMessage, DeleteItemConfirmMessageToken);
                });
        }

        /// <summary>
        /// Get the message of RequestSetItemAsDone from TodoItemTreeViewItemViewModel. 
        /// Update TodoItem as Done and remove it from the unfinished item collection.
        /// </summary>
        private void RegisterRequestSetItemAsDoneMessage()
        {
            Messenger.Default.Register<TodoItem>(this, TodoItemTreeViewItemViewModel.RequestSetItemAsDoneMessageToken, item =>
                {
                    try
                    {
                        SetCurrentTodoLabel(item.ID);
                        SetItemAsDone(item.ID);
                        RemoveSelectedItemFromCollection(item.ID);
                    }
                    catch (Exception ex)
                    {
                        RequestDisplayErrorMessage(ex.Message);
                    }
                });
        }

        /// <summary>
        /// Get the message of RequestSetItemAsUndoneMessage from TodoItemTreeViewItemViewModel. 
        /// Insert this item into unfinish item collection.
        /// </summary>
        private void RegisterRequestSetItemAsUndoneMessage()
        {
            Messenger.Default.Register<TodoItem>(this, TodoItemTreeViewItemViewModel.RequestSetItemAsUndoneMessageToken, item =>
            {
                try
                {
                    var newItem = ViewModelHelper.CreateNewUnfinishedTodoItemTreeViewItemViewModel(CurrentDataService, item);

                    SetCurrentTodoLabel(item.ID);
                    InsertSelectedItemIntoCollection(newItem);
                }
                catch (Exception ex)
                {
                    RequestDisplayErrorMessage(ex.Message);
                }
            });
        }

        /// <summary>
        /// Get the message of StartSearchTodoItemMessageToken from TodoItemSearcherViewModel to 
        /// search the matched TodoItems based on provided keyword.
        /// </summary>
        private void RegisterStartSearchTodoItemMessage()
        {
            Messenger.Default.Register<string>(this, TodoItemSearcherViewModel.StartSearchTodoItemMessageToken, keyword => 
                {
                    SearchTodoItem(keyword);
                });
        }

        /// <summary>
        /// Search the matched TodoItems based on provided keyword.
        /// </summary>
        /// <param name="keyword">Provided keyword</param>
        private void SearchTodoItem(string keyword)
        {
            keyword = keyword.ToLower();
            var matchedItemCount = 0;

            if (!string.IsNullOrEmpty(keyword))
            {
                // The result is not need to be used in somewhere else because the matched items in TodoItems has been updated.
                // The UI will be auto updated based on TodoItems.
                //var result = from item in TodoItems where !item.Title.Contains(keyword) select ChangeIsMatchedValue(item, false);

                foreach (var item in TodoItems)
                {
                    if (item.Title.ToLower().Contains(keyword))
                    {
                        item.IsMatched = true;
                        matchedItemCount += 1;
                    }
                    else
                    {
                        item.IsMatched = false;
                    }
                }

                SetItemInfoSummary(matchedItemCount);
            }
            else
            {
                foreach (var item in TodoItems)
                {
                    item.IsMatched = true;
                }

                SetItemInfoSummary();
            }
        }

        /// <summary>
        /// Delete specified TodoItem from data source.
        /// </summary>
        /// <param name="itemID">The specified TodoItem ID.</param>
        private void DeleteSelectedItemFromDataSource(int itemID)
        {
            TodoItem todoItem;

            // Get the TodoItem from data source. If CurrentTodoLabel is null then need to load it from data source.
            if (CurrentTodoLabel == null)
            {
                todoItem = _todoItemService.Get(itemID, true);
                CurrentTodoLabel = new TodoLabel { ID = todoItem.Label.ID };
            }
            else
            {
                CurrentTodoLabel = new TodoLabel { ID = CurrentTodoLabel.ID };
                todoItem = _todoItemService.Get(itemID);
            }

            if (todoItem != null)
            {
                // Delete the TodoItem from data source.
                _todoItemService.Delete(todoItem);
            }
        }

        /// <summary>
        /// Remove the specified TodoItem from collection that used by UI, update the summary info and inform all obervers.
        /// </summary>
        /// <param name="itemID">The specified TodoItem ID.</param>
        private void RemoveSelectedItemFromCollection(int itemID)
        {
            // Get the TodoItemTreeViewModel.
            var selectedItem = (from item in TodoItems where item.TodoItemID == itemID select item).FirstOrDefault();

            if (selectedItem != null)
            {
                // Remove the TodoItemTreeViewModel from the TreeViewItem collection.
                TodoItems.Remove(selectedItem);

                // Get FitlerType to update Filter List TodoItem Count.
                FilterType = GeneralUtilities.GetFilterType(selectedItem.DueDate);

                // Refresh the item info summary.
                SetItemInfoSummary();

                // Notify all observers.
                TodoItemsChangedNotify(TodoItemActionCategory.Done);
            }
        }

        /// <summary>
        /// Insert the selected TodoItem into current collection on the top. Show the summary message and notify all observers.
        /// </summary>
        /// <param name="item">The selected TodoItem</param>
        private void InsertSelectedItemIntoCollection(TodoItemTreeViewItemViewModel item)
        {
            // Insert this item into current collecton on the top.
            TodoItems.Insert(0, item);

            // Get FitlerType to update Filter List TodoItem Count.
            FilterType = GeneralUtilities.GetFilterType(item.DueDate);

            // Refresh the item info summary.
            SetItemInfoSummary();

            // Notify all observers.
            TodoItemsChangedNotify(TodoItemActionCategory.Undone);
        }

        /// <summary>
        /// Set specified TodoItem as done and update to data source.
        /// </summary>
        /// <param name="itemID">The specified TodoItem's ID.</param>
        private void SetItemAsDone(int itemID)
        {
            // Get the TodoItem from data source.
            var todoItem = _todoItemService.Get(itemID);

            if (todoItem != null)
            {
                // Update the TodoItem as is done in data source.
                todoItem.IsDone = true;

                _todoItemService.Update(todoItem);
            }
        }

        /// <summary>
        /// Set CurrentTodoLabel value by provided TodoItem ID.
        /// </summary>
        /// <param name="itemID">The provided TodoItem ID.</param>
        private void SetCurrentTodoLabel(int itemID)
        {
            CurrentTodoLabel = new TodoLabel
            {
                ID = _todoItemService.Get(itemID).Label.ID
            };
        }
    }
}